Admin.jsを使って面倒な管理画面をサクッと作ろう
こんにちは、CX事業本部Delivery部サーバーサイドチームのmorimorkochanです。
突然ですが「あぁ〜管理画面作るのめんどくせ〜」って思うことはないですか?
例えばRDBと接続されたRESTfulなAPIサーバーを作っていて、一部の管理者向けに管理画面を作りたいが管理画面にこだわりがない場合などなど。
そんな時に便利なのが、Admin.jsです。Admin.jsは管理画面を簡単に作成できるフレームワークです。オープンソースとして公開されており、クラウドにデプロイされているサービスを利用する場合は月額料金がかかりますが手動でサーバーに組み込んでデプロイする場合は無料です。 Admin.jsを使うと、RDBで管理される各テーブルごとにCRUD画面を簡単に作成することができます。これによってRDBと同じプロパティを何度も定義したり同じようなCRUDコードを何度も記述する必要はありません。
今回は、Prismaが利用されているExpress製サーバーに対してAdmin.jsを導入し、管理画面を作り、自分で操作をカスタマイズしてみたので紹介します。
環境
- Node.js v18.16.1
- Admin.js v7.2.1
- Express v4.18.2
- Prisma v5.3.0
導入対象のExpress製サーバー
Admin.jsを導入する対象となるサーバーは、prisma-examplesに用意されているrest-expressを使ってみます。
以下の手順でDB(sqlite3)を用意しサーバーを立ち上げます
npx try-prisma@latest --template typescript/rest-express cd typescript_rest-express # DBの準備 npx prisma migrate dev # Expressサーバー起動 # Admin.jsはCommonJSでは動作しないので`--esm` npm run dev --esm
この状態で`http://localhost:3000/feed`を叩くと以下のようなデータが返却されると思います
[{"id":1,"createdAt":"2023-09-14T10:27:27.601Z","updatedAt":"2023-09-14T10:27:27.601Z","title":"Join the Prisma Slack","content":"https://slack.prisma.io","published":true,"viewCount":0,"authorId":1,"author":{"id":1,"email":"alice@prisma.io","name":"Alice"}},{"id":2,"createdAt":"2023-09-14T10:27:27.605Z","updatedAt":"2023-09-14T10:27:27.605Z","title":"Follow Prisma on Twitter","content":"https://www.twitter.com/prisma","published":true,"viewCount":0,"authorId":2,"author":{"id":2,"email":"nilu@prisma.io","name":"Nilu"}},{"id":3,"createdAt":"2023-09-14T10:27:27.606Z","updatedAt":"2023-09-14T10:27:27.606Z","title":"Ask a question about Prisma on GitHub","content":"https://www.github.com/prisma/prisma/discussions","published":true,"viewCount":0,"authorId":3,"author":{"id":3,"email":"mahmoud@prisma.io","name":"Mahmoud"}}]
このDBにはUser
テーブルとPost
テーブルの2つが用意されており、データもすでに入っている状態です。
Admin.jsを導入する
Admin.jsでは、さまざまなタイプのサーバーに対して管理画面を提供できるように、プラグインという各サーバーに対応したモジュールが提供されています。今回はExpress製サーバーに対応したプラグインを利用します。
導入方法が公式で提供されていて、それに従うだけでうまくいったのでここは省略します。
また、Admin.jsはさまざまなタイプのデータベースに対応していて、アダプターという対応したモジュールが提供されています。今回はPrismaを利用しているので、Prisma用のアダプターを利用します。
導入方法は公式で提供されてます。
ですが、Prismaのv4.14.0以降、PrismaClient
から_basedmmf
が削除されているため上記手順では動きません
- https://github.com/SoftwareBrothers/adminjs-prisma/issues/35
- https://github.com/SoftwareBrothers/adminjs/issues/1506
なので代わりにAdminJSPrisma.getModelByName()
を利用する必要があります。どうやら公式ドキュメントの更新が追いついていないようです
const adminOptions = { resources: [{ - resource: { model: dmmf.modelMap.Publisher, client: prisma }, + resource: { model: AdminJSPrisma.getModelByName('Publisher'), client: prisma }, options: {}, }], }
また、テーブルごとにresouce
の定義は必要なため、PostテーブルとUserテーブルで有効にする場合は以下のようになります。
const adminOptions = { resources: [ {resource: { model: AdminJSPrisma.getModelByName('Post'), client: prisma },options: {}}, {resource: { model: AdminJSPrisma.getModelByName('User'), client: prisma },options: {}} ], }
ここで`http://localhost:3000/admin`を開いてみると、サイドメニューにresouceで指定された各テーブルが表示されており、一覧表示・詳細表示・登録・編集・削除のいずれもできるようになっています。
上記画像のように、一覧表示画面で検索ができるようになっていたり、編集画面で外部キーのカラムは自動で対象のテーブルのレコードをプルダウンで表示してくれたりと細かいところまで使いやすくなっています、超有能です。 しかもSPAで動作しているので、サクサク動きます。
認証機能を入れる
このままでは/admin
を開くと誰でも管理画面を利用できてしまいますが、Admin.jsでは簡単に認証機能を入れることが可能です。
const adminRouter = AdminJSExpress.buildAuthenticatedRouter(admin, { cookiePassword: "awesomeCookiePassword", authenticate: async (email, password) => { // ここでemailとpasswordで認証する処理を書く if (email === "superuser@example.com" && password === "awesomePassword") { // 認証成功時はユーザー情報を返す return { email, password } } // 認証失敗時はnullを返す return null } })
再起動してアクセスするとこんな感じの認証画面にリダイレクトされます。思ったよりちゃんとしてますね
上記のコードを見てわかる通り、認証情報はサーバーのセッションで管理されており、メモリに保存されています。 なので、サーバーが再起動される環境や複数台のサーバーでロードバランシングしている環境には不向きです(後者はスティッキーセッションを利用して解消することが可能ですが)
操作をカスタマイズする
Admin.jsではデータの操作として登録/編集/削除が用意されていますが、オリジナルの操作を追加することも簡単に可能です。
例えば、Post
テーブルの特定の単一レコードに対して記事を公開するpublish
という操作を行いたい場合は、アクションを新たに定義します
アクションは以下のようにresource
のactions
にプロパティを追加することで可能です。
const publishActionHandler: ActionHandler<RecordActionResponse> = async (request, response, context) => { const post = context.record! post.set('published', true) await post.save(context) return { notice: { message: "公開されました🚀" }, record: post.toJSON(context.currentAdmin) } } const postResource = { resource: { model: AdminJSPrisma.getModelByName('Post'), client: prisma }, options: { actions: { publish: { actionType: 'record', component: false, handler: publishActionHandler, }, }, }, } const admin = new AdminJS({ rootPath: '/admin', resources: [ postResource, {resource: { model: AdminJSPrisma.getModelByName('User'), client: prisma },options: {},} ], })
操作対象のレコードはcontext
のrecord
プロパティで取得できるのですが型安全ではないため、実際に利用する際は追加で型注釈をしてあげると良いと思います。また、context
にはcurrentAdmin
というプロパティがあり、現在ログインしているユーザーの情報が格納されています。
実際に操作するとこんな感じです。操作完了後にnotice.message
に指定したメッセージが表示されていてなおかつpublished
がYes
になっていることがわかります。
また、他にもさまざまなオプションが用意されています。
actionType
をrecord
からbulk
にすることで複数レコードに対して操作する(例:複数レコードを削除する)isAccessible
を使って誰が操作できるかを制御するcomponent
を使ってUIをカスタマイズする
感想
- 今回紹介した以外にもいろいろな機能が提供されています。
- ファイルをAWS S3やGoogle Cloud Storageにアップロードする機能
- 一括でデータのインポートエクスポートができる機能
- 管理画面の各種操作を全て記録する機能
- 特にReactでUIをカスタマイズできる点は本当に優れてると思います。実際の開発では、徐々にUIがカスタマイズされていってテンプレートエンジンでは複雑性が高くなり開発できなくなる、みたいなことがなさそうに感じます
- 日本語には未対応です。i18nの設定は存在しますがsrc/locale日本語の設定はありません
- Python/DjangoのDjangoAdminがとても使いやすく爆速で開発できた印象があったので、Node.jsでも同等のものがないかを今回は探してみました
- サードパーティーのライブラリの数や種類はDjangoAdminには及びませんが、基本的なCRUD操作はもちろんのこと、欲しい機能があらかた揃っているので、とても満足しています。
- 強いて不満点を言うなら
- 処理をカスタマイズする際に型安全ではないところもあるのでそこは要注意です。
- ドキュメントが古いところがあったり不足しているところがあるので、自分でガリガリ型定義を読む必要があります。
- それでも動的型付け言語の場合と比べると、型定義を読むことでどういう処理をすれば良いかがわかるので、そこまで苦ではありませんでした。
DMMF
という概念を初めて知ったのですが、Data Model Meta Formatというモデルのメタ情報のことを指しているようです。- https://github.com/prisma/prisma/blob/main/ARCHITECTURE.md#the-dmmf-or-data-model-meta-format
- ググった感じだと
Domain Modeling Made Functional
を略してもこう呼ばれるみたいです
是非みなさんもAdmin.jsを一度使ってみてください。